#include "base_app.hpp"
#include "core/log.h"
#include "utils/path_utils.hpp"

#include <imgui/imgui_freetype.h>
#include <imgui/imgui_impl_glfw.h>
#include <imgui/imgui_impl_opengl3.h>

class Callbacks {
    static App *GetApp(GLFWwindow *win) { return reinterpret_cast<App *>(glfwGetWindowUserPointer(win)); }

public:
    static void WindowPosCallback(GLFWwindow *w, int x, int y) {
        const auto app = GetApp(w);
        app->OnWindowMoved(x, y);
        app->Update();
    }

    static void WindowSizeCallback(GLFWwindow *w, int width, int height) {
        const auto app = GetApp(w);
        app->OnWindowResized(width, height);
        app->Update();
    }

    static void KeyCallback(GLFWwindow *w, int key, int, int action, int mods) {
        const auto app = GetApp(w);
        const bool is_press = action == GLFW_PRESS || action == GLFW_REPEAT;
        app->OnKey(key, mods, is_press);
        app->Update();
    }

    static void MouseButtonCallback(GLFWwindow *w, int button, int action, int mods) {
        const auto app = GetApp(w);
        const bool is_press = action == GLFW_PRESS;
        app->OnMouseClicked(button, mods, is_press);
        app->Update();
    }

    static void WheelCallback(GLFWwindow *w, double dx, double dy) {
        const auto app = GetApp(w);
        app->OnWheelScroll((int)dx, (int)dy);
        app->Update();
    }

    static void CursorPosCallback(GLFWwindow *w, double dx, double dy) {
        const auto app = GetApp(w);
        app->OnMouseMove(dx, dy);
//        app->Update();
    }
};

App::App(GLFWwindow *window, const char *name) : m_window_(window), m_config_ui_enabled_(true) {
    glfwSetWindowTitle(window, name);

    glfwSetWindowUserPointer(window, this);
    glfwSetWindowPosCallback(window, &Callbacks::WindowPosCallback);
    glfwSetWindowSizeCallback(window, &Callbacks::WindowSizeCallback);
    glfwSetKeyCallback(window, &Callbacks::KeyCallback);
    glfwSetMouseButtonCallback(window, &Callbacks::MouseButtonCallback);
    glfwSetScrollCallback(window, &Callbacks::WheelCallback);
    glfwSetCursorPosCallback(window, &Callbacks::CursorPosCallback);

    // IMGUI Stuff
    ImGuiInit();

    m_window_width_ = m_window_height_ = 0;
    glfwGetWindowSize(window, &m_window_width_, &m_window_height_);
    m_need_resize_viewport_ = true;
    m_time_ = m_time_offset_ = 0.0;
    m_clear_color_ = glm::vec4{0.3f,0.3f,0.3f,1.f};
}

App::~App()
{
    ImGuiCleanup();
}

void App::OnInitialize() {
    m_vsync_enabled_ = true;
    glfwSwapInterval(m_vsync_enabled_ ? 1 : 0);
}

void App::OnCleanup() {
}

void App::Update() {
    if (m_need_resize_viewport_) {
        GLCallValidation(glViewport(0, 0, m_window_width_, m_window_height_));
        m_need_resize_viewport_ = false;
    }

    GLCallValidation(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT));
    GLCallValidation(glClearColor(m_clear_color_.x, m_clear_color_.y, m_clear_color_.z, m_clear_color_.w));

    // time calculate
    m_frame_time_ = 0.0;
    if (m_time_offset_ == 0.0) {
        m_time_offset_ = glfwGetTime();
        m_frame_time_ = m_time_ = 0.0;
    } else {
        const double current = glfwGetTime();
        m_frame_time_ = current - m_time_offset_ - m_time_;
        m_time_ += m_frame_time_;
    }

    RenderFrame(m_frame_time_, m_time_);

    if (m_config_ui_enabled_)
        RenderGui();

    GLCallValidation(glfwSwapBuffers(m_window_));
}

void App::Exit()
{
    glfwSetWindowShouldClose(m_window_, GLFW_TRUE);
}

void App::OnWindowResized(int w, int h) {
    m_window_height_ = h;
    m_window_width_ = w;

    m_need_resize_viewport_ = true;
}

void App::OnWindowMoved(int x, int y) {
    (void) x;
    (void) y;
}

void App::OnKey(int key, int modifiers, bool is_press) {
    (void) modifiers;
    (void) is_press;

    if (key == GLFW_KEY_ESCAPE)
        Exit();
    else if (key == GLFW_KEY_F1 && !is_press)
        m_config_ui_enabled_ = !m_config_ui_enabled_;
    else if (key == GLFW_KEY_F2 && !is_press)
    {
        m_vsync_enabled_ = !m_vsync_enabled_;
        glfwSwapInterval((int)m_vsync_enabled_);
    }
}

void App::OnMouseClicked(int button, int mods, bool is_press) {
    (void) button;
    (void) mods;
    (void) is_press;
}

void App::OnWheelScroll(int dx, int dy) {
    (void) dx;
    (void) dy;
}

void App::OnMouseMove(double x, double y) {
    (void)x;
    (void)y;
}

struct AppCreatorRegistry {
    const char *name;
    PFN_CreateApp factory;
};

constexpr int MAX_APP_TYPE = 1024;
static int registered_type = 0;
static AppCreatorRegistry s_registry[MAX_APP_TYPE];

void RegisterAppCreate(const char *name, PFN_CreateApp factory) {
    if (registered_type < MAX_APP_TYPE) {
        // check if name already exists
        for (int i = 0; i < registered_type; ++i) {
            const auto &r = s_registry[i];
            if (strcmp(name, r.name) == 0) {
                LOG_E("Name \'%s\' Already Exists!!", name);
                return;
            }
        }

        AppCreatorRegistry &r = s_registry[registered_type++];
        r.name = name;
        r.factory = factory;
    }
}

void App::RenderConfigUI() {
    if (ImGui::TreeNodeEx("基础设置", ImGuiTreeNodeFlags_Framed)) {
        ImGui::AlignTextToFramePadding();
        ImGui::Text("帧率：%.2f", ImGui::GetIO().Framerate);
        ImGui::Text("帧时间：%.4f", m_frame_time_);
        ImGui::Text("运行时间：%.4f", m_time_);
        ImGui::TextUnformatted("垂直同步：");
        ImGui::SameLine();
        if (ImGui::Checkbox("##V-Sync", &m_vsync_enabled_))
            glfwSwapInterval(m_vsync_enabled_ ? 1 : 0);
        ImGui::Vector4Ranged("背景色", m_clear_color_, glm::vec4{0.f}, glm::vec4{1.f}, glm::vec4{ .3f, .3f, .3f, 1.f }, 0.01f);
        ImGui::TreePop();
    }
}

void App::ImGuiInit() {
    using namespace ImGui;
    IMGUI_CHECKVERSION();

    CreateContext();
    auto &io = GetIO();

    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableSetMousePos;

    io.ConfigWindowsResizeFromEdges = false;
    io.ConfigWindowsMoveFromTitleBarOnly = true;
    io.IniFilename = nullptr;
    io.LogFilename = nullptr;

    StyleColorsDark();

    ImGui_ImplGlfw_InitForOpenGL(m_window_, true);
    ImGui_ImplOpenGL3_Init("#version 300 es");

    InitFonts();

    // Colors
    ImVec4* colors = ImGui::GetStyle().Colors;
    colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
    colors[ImGuiCol_TextDisabled] = ImVec4(0.63f, 0.63f, 0.63f, 1.00f);
    colors[ImGuiCol_WindowBg] = ImVec4(0.15f, 0.22f, 0.30f, 1.00f);
    colors[ImGuiCol_ChildBg] = ImVec4(0.32f, 0.33f, 0.34f, 1.00f);
    colors[ImGuiCol_PopupBg] = ImVec4(0.16f, 0.24f, 0.23f, 1.00f);
    colors[ImGuiCol_Border] = ImVec4(0.22f, 0.41f, 0.72f, 0.58f);
    colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
    colors[ImGuiCol_FrameBg] = ImVec4(0.10f, 0.17f, 0.24f, 1.00f);
    colors[ImGuiCol_FrameBgHovered] = ImVec4(0.14f, 0.17f, 0.11f, 1.00f);
    colors[ImGuiCol_FrameBgActive] = ImVec4(0.18f, 0.18f, 0.04f, 1.00f);
    colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
    colors[ImGuiCol_TitleBgActive] = ImVec4(0.05f, 0.15f, 0.29f, 1.00f);
    colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
    colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
    colors[ImGuiCol_ScrollbarBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
    colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
    colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
    colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
    colors[ImGuiCol_CheckMark] = ImVec4(0.93f, 0.93f, 0.93f, 1.00f);
    colors[ImGuiCol_SliderGrab] = ImVec4(0.93f, 0.93f, 0.93f, 1.00f);
    colors[ImGuiCol_SliderGrabActive] = ImVec4(0.42f, 0.57f, 0.09f, 1.00f);
    colors[ImGuiCol_Button] = ImVec4(0.42f, 0.57f, 0.09f, 1.00f);
    colors[ImGuiCol_ButtonHovered] = ImVec4(0.48f, 0.66f, 0.10f, 1.00f);
    colors[ImGuiCol_ButtonActive] = ImVec4(0.15f, 0.20f, 0.03f, 1.00f);
    colors[ImGuiCol_Header] = ImVec4(0.18f, 0.20f, 0.22f, 1.00f);
    colors[ImGuiCol_HeaderHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);
    colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
    colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
    colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);
    colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f);
    colors[ImGuiCol_ResizeGrip] = ImVec4(0.66f, 0.81f, 0.98f, 0.20f);
    colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.76f, 0.87f, 1.00f, 0.67f);
    colors[ImGuiCol_ResizeGripActive] = ImVec4(0.83f, 0.91f, 1.00f, 0.95f);
    colors[ImGuiCol_Tab] = ImVec4(0.37f, 0.42f, 0.42f, 0.86f);
    colors[ImGuiCol_TabHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
    colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.41f, 0.68f, 1.00f);
    colors[ImGuiCol_TabUnfocused] = ImVec4(0.07f, 0.10f, 0.15f, 0.97f);
    colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.26f, 0.42f, 1.00f);
//    colors[ImGuiCol_DockingPreview] = ImVec4(0.26f, 0.59f, 0.98f, 0.70f);
//    colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
    colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
    colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
    colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
    colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
    colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
    colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
    colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
    colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
    colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
    colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
    colors[ImGuiCol_DragDropTarget] = ImVec4(0.78f, 1.00f, 0.59f, 0.90f);
    colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
    colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
    colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
    colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);

    auto& style = ImGui::GetStyle();
    // Size
    style.FrameRounding = 4.f;
    style.PopupRounding = 4.f;
    style.ChildRounding = 4.f;
    style.WindowRounding = 4.f;
    style.GrabRounding = 4.f;
    style.ScrollbarRounding = 4.f;
    style.WindowPadding = { 4.f, 4.f };
    style.FramePadding = { 4.f, 4.f };
    style.ItemSpacing = { 8.f, 4.f };
}

void App::ImGuiCleanup() {
    using namespace ImGui;
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    DestroyContext();
}

void App::RenderGui() {
    using namespace ImGui;

    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    NewFrame();

    const uint32_t flags = UIFlags();

    if (flags & UF_CONFIG) {
        SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver);
        SetNextWindowSize(ImVec2(GetFontSize() * 18, 0), ImGuiCond_Always);
        if (Begin("程序配置", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
            RenderConfigUI();
        End();
    }

//    ImGui::ShowDemoWindow();

    // fully control by subclass ui
    if (flags & UF_ALONE) {
        StandaloneUI();
    }

    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}

void App::InitFonts() {
    constexpr float fontSize = 18.f;

    auto& io = ImGui::GetIO();
    io.Fonts->Clear();

    constexpr int order[] = { 2, 3, 1, 4 };
    bool first = true;

    for (auto i : order)
    {
        switch (i)
        {
            case 1:
            {
                // wqy-zenhei
                std::string path = (std::filesystem::path{Utils::GetFontDirPath()} / "wqy-microhei.ttc").string();
                ImFontConfig wqy_config;
                strcpy(wqy_config.Name, "WQY 米黑");
                wqy_config.MergeMode = !first;
                wqy_config.PixelSnapH = true;
                wqy_config.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LoadColor;
                auto font = io.Fonts->AddFontFromFileTTF(path.data(), fontSize, &wqy_config,
                                                         io.Fonts->GetGlyphRangesChineseFull());
                if (!font)
                    abort();
            }
            break;
            case 2:
            {
                // fontawesome
                std::string path = (std::filesystem::path{Utils::GetFontDirPath()} / "fontawesome-webfont.ttf").string();
                static const ImWchar icons_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0, 0 };
                ImFontConfig icons_config;
                strcpy(icons_config.Name, "Font Awesome 5");
                icons_config.MergeMode = !first;
                icons_config.PixelSnapH = true;
                icons_config.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LoadColor;
                auto font = io.Fonts->AddFontFromFileTTF(path.data(),
                                                         fontSize - 2, &icons_config, icons_ranges);
                if (!font)
                    abort();
            }
            break;
            case 3:
            {
                // source code pro
                std::string path = (std::filesystem::path{Utils::GetFontDirPath()} / "SourceCodePro.ttf").string();
                ImFontConfig icons_config;
                strcpy(icons_config.Name, "Source Code Pro Regular");
                icons_config.MergeMode = !first;
                icons_config.PixelSnapH = true;
                icons_config.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_LoadColor;
                auto font = io.Fonts->AddFontFromFileTTF(path.data(),
                                                         fontSize, &icons_config, io.Fonts->GetGlyphRangesDefault());
                if (!font)
                    abort();
            }
            break;
            // case 4:
            // {
            //     // source code pro
            //     std::string path = "assets/NotoColorEmoji.ttf"_AP;
            //     // std::string path = R"(C:\Windows\Fonts\seguiemj.ttf)";
            //     ImWchar ranges[] = { 1,0x1FFFF,0 };
            //     ImFontConfig icons_config;
            //     strcpy(icons_config.Name, "Noto Color Emoji");
            //     icons_config.MergeMode = !first;
            //     icons_config.PixelSnapH = true;
            //     icons_config.FontBuilderFlags = (ImGuiFreeTypeBuilderFlags_Bitmap | ImGuiFreeTypeBuilderFlags_LoadColor);
            //     auto font = io.Fonts->AddFontFromFileTTF(path.data(),
            //         fontSize, &icons_config, ranges);
            //     if (!font)
            //         abort();
            // }
            // break;
            default:
                break;
        }
        first = false;
    }
    bool built = io.Fonts->Build();
    IM_ASSERT(built && "Font Must be built!");
}
void App::StandaloneUI() {

}

uint32_t App::UIFlags() const {
    return UF_CONFIG;
}

UniqueApp CreateApp(const std::string &name, GLFWwindow *window, const std::vector<std::string> &args) {
    // check if name already exists
    for (int i = 0; i < registered_type; ++i) {
        auto &r = s_registry[i];
        if (name == r.name) {
            return r.factory(window, args);
        }
    }
    LOG_F("Type \"%s\" Not Found!", name.c_str());
    assert(0 && "App Type Not Found!");
    return {};
}
